提醒:今天的內容缺少了加密儲存密碼,是極度危險的功能,這部份預計會放到明天處理。
Layout的部份我一樣使用Chip取代Checkbox,在Login頁面中加入:
<!-- ... -->
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
app:layout_constraintBottom_toTopOf="@id/login"
app:layout_constraintEnd_toEndOf="@id/pwdLayout"
app:layout_constraintStart_toStartOf="@id/pwdLayout"
app:layout_constraintTop_toBottomOf="@id/pwdLayout">
<com.google.android.material.chip.Chip
android:id="@+id/saveId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
android:fontFamily="sans-serif"
android:text="記住ID"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.chip.Chip
android:id="@+id/savePwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
android:fontFamily="sans-serif"
android:text="記住密碼"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.chip.Chip
android:id="@+id/auto_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
android:fontFamily="sans-serif"
android:text="自動登入"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="gone" />
</com.google.android.material.chip.ChipGroup>
<!-- ... -->
儲存的內容會存到SharePreferences中,先做一些前置作業:
const val PREF_NAME = "preferences"
const val PREF_FIELD_ID = "id"
const val PREF_FIELD_PWD = "pwd"
const val PREF_FIELD_AES_KEY = "aes_key"
const val PREF_FIELD_AUTO_LOGIN = "auto_login"
class LoginFragment : Fragment() {
// ...
private lateinit var preferences: SharedPreferences
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
preferences = view.context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
}
}
PREF_NAME是在Day21中已加入的值,其餘是這兩天會用到的。
儲存的時機點我是放在登入成功時,以下片段節自Day07
// ...
4 -> { // "【主功能表】"
withContext(Dispatchers.Main) {
val editor = preferences.edit()
if (binding.saveId.isChecked) {
editor.putString(PREF_FIELD_ID, id)
}
if (binding.savePwd.isChecked) {
editor.putString(PREF_FIELD_PWD, pwd)
}
editor.putBoolean(
PREF_FIELD_AUTO_LOGIN,
binding.autoLogin.isChecked
)
editor.apply()
(requireActivity() as MainActivity).dismissLoading()
NavHostFragment.findNavController(this@LoginFragment)
.navigate(R.id.action_loginFragment_to_searchArticleFragment)
}
break
}
// ...
提取的位置我是放在開始執行進入動畫前:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ...
binding.chipGroup.postDelayed({
val id = preferences.getString(PREF_FIELD_ID, null)
val pwd = preferences.getString(PREF_FIELD_PWD, null)
if (!id.isNullOrBlank()) {
binding.idInput.setText(id)
binding.saveId.isChecked = true
}
if (!pwd.isNullOrBlank()) {
binding.pwdInput.setText(pwd)
binding.savePwd.isChecked = true
}
if (binding.saveId.isChecked && binding.savePwd.isChecked) {
binding.autoLogin.visibility = View.VISIBLE
binding.autoLogin.isChecked =
preferences.getBoolean(PREF_FIELD_AUTO_LOGIN, false)
}
enterAnimate(binding.chipGroup)
}, 267)
// ...
}
autoLogin的檢查條件除了本身是否紀錄有勾選外,還需要多判斷是否有已儲存的帳密,此外autoLogin的按鈕我預設是隱藏的,只有在saveId和savePwd都被打勾的狀態下才能勾選。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ...
binding.saveId.setOnCheckedChangeListener { _, isChecked ->
checkShowAutoLogin()
if (!isChecked) {
preferences.edit().remove(PREF_FIELD_ID).apply()
}
}
binding.savePwd.setOnCheckedChangeListener { _, isChecked ->
checkShowAutoLogin()
if (!isChecked) {
preferences.edit().remove(PREF_FIELD_PWD).apply()
}
}
binding.autoLogin.setOnCheckedChangeListener { _, isChecked ->
preferences.edit().putBoolean(PREF_FIELD_AUTO_LOGIN, isChecked).apply()
}
// ...
}
private fun checkShowAutoLogin() {
if (binding.saveId.isChecked && binding.savePwd.isChecked) {
binding.autoLogin.visibility = View.VISIBLE
} else {
binding.autoLogin.visibility = View.GONE
binding.autoLogin.isChecked = false
}
}
如前所述autoLogin只有在saveId和savePwd都被打勾的狀態下才能勾選,因此這兩個按鈕在點擊時會去呼叫checkShowAutoLogin來判斷目前狀態,其餘內容就是取消勾選時移除目前已儲存的資料。
在處理自動登入時,除了autoLogin的勾選狀態外,還需要考慮到登出和斷線重連的狀態。在這兩個狀態中我們不該執行自動登入的功能。
為了處理上述兩個狀態,我分別在loginFragment的fragment tag和welcome_to_login的action tag中加入Argument。
<argument
android:name="canAutoLogin"
android:defaultValue="false"
app:argType="boolean"
app:nullable="false" />
<argument
android:name="canAutoLogin"
android:defaultValue="true"
app:argType="boolean"
app:nullable="false" />
可以看到兩者的差異只差在defaultValue,只有在從WelcomeFragment到LoginFragment的action中才會將canAutoLogin設為true
。
有了這個參數值,就可以做以下判斷:
// ...
if (preferences.getBoolean(PREF_FIELD_AUTO_LOGIN, false)
&& LoginFragmentArgs.fromBundle(requireArguments()).canAutoLogin
) {
binding.login.performClick()
}
// ...
至此自動登入功能也就完成了。
最後再次提醒:今天的內容缺少了加密儲存密碼,是極度危險的功能,這部份預計會放到明天處理。